import type { Mark, Node } from "@rivet/model";
import type { Step, StepResult } from "./step.ts";
import { AttrStep, TrStep } from "./attr_step.ts";
import { createDraft, type Draft, finishDraft, type Patch } from "immer";
import { AddMarkStep, RemoveMarkStep } from "./mark_step.ts";

export class TransformError extends Error {
  constructor(message: string) {
    super(message);
    this.name = "TransformError";
  }
}

export class Transform {
  steps: Step[] = [];
  docs: Node[] = [];
  draft: Draft<Node> | null = null;

  constructor(
    public doc: Node,
  ) {}
  transaction(callback: (data: Draft<Node>) => void): this {
    try {
      this.draft = createDraft(this.doc);
      callback(this.draft);
      this.commitTransaction();
    } catch (e: unknown) {
      this.rollbackTransaction();
      throw e;
    }
    return this;
  }

  async asyncTransaction(
    callback: (data: Draft<Node>) => Promise<void> | void,
  ): Promise<this> {
    try {
      this.draft = createDraft(this.doc);
      await callback(this.draft);
      this.commitTransaction();
    } catch (e: unknown) {
      this.rollbackTransaction();
      throw e;
    }
    return this;
  }
  private rollbackTransaction() {
    this.draft = null;
  }
  private commitTransaction() {
    const changes: Patch[] = [];
    const inverseChanges: Patch[] = [];
    this.doc = finishDraft(this.draft, (patches, inversePatches) => {
      changes.push(...patches);
      inverseChanges.push(...inversePatches);
    }) || this.doc;
    this.addStep(new TrStep(changes, inverseChanges), this.doc);
    this.draft = null;
  }

  get before(): Node {
    return this.docs.length ? this.docs[0] : this.doc;
  }

  step(step: Step): Transform {
    const result = this.maybeStep(step);
    if (result) {
      if (result.failed) throw new TransformError(result.failed);
    }
    return this;
  }

  maybeStep(step: Step): StepResult | undefined {
    const result = step.apply(this.draft, this.doc);
    if (result) {
      if (!result.failed) this.addStep(step, result.doc!);
    }
    return result;
  }

  get docChanged(): boolean {
    return this.steps.length > 0;
  }

  /// @internal
  addStep(step: Step, doc: Node) {
    this.docs.push(this.doc);
    this.steps.push(step);
    this.doc = doc;
  }
  ///设置节点属性
  setNodeAttribute(id: string, value: any): this {
    this.step(new AttrStep(id, value));
    return this;
  }

  ///添加标记
  addNodeMark(id: string, mark: Mark): this {
    this.step(new AddMarkStep(id, mark));
    return this;
  }
  //移除标记
  removeMark(id: string, filter: (mark: Mark) => boolean): this {
    this.step(new RemoveMarkStep(id, filter));
    return this;
  }
}
